/*------------------------------------------------------------------------------
#
#	Apple Macintosh Developer Technical Support
#
#	MultiFinder-Aware Simple TextEdit Sample Application
#
#	CPlusTESample
#
#	TApplication.cp		-	C++ source
#
#	Copyright © 1989 Apple Computer, Inc.
#	All rights reserved.
#
#	Versions:	
#			1.00 					04/89
#
#	Components:
#			CPlusTESample.make		April 1, 1989
#			TApplicationCommon.h	April 1, 1989
#			TApplication.h			April 1, 1989
#			TDocument.h				April 1, 1989
#			TECommon.h				April 1, 1989
#			TESample.h				April 1, 1989
#			TEDocument.h			April 1, 1989
#			TApplication.cp			April 1, 1989
#			TDocument.cp			April 1, 1989
#			TESample.cp				April 1, 1989
#			TEDocument.cp			April 1, 1989
#			TESampleGlue.a			April 1, 1989
#			TApplication.r			April 1, 1989
#			TESample.r				April 1, 1989
#
#	CPlusTESample is an example application that demonstrates
#	how to initialize the commonly used toolbox managers,
#	operate successfully under MultiFinder, handle desk
#	accessories and create, grow, and zoom windows. The
#	fundamental TextEdit toolbox calls and TextEdit autoscroll
#	are demonstrated. It also shows how to create and maintain
#	scrollbar controls. 
#
#	This version of TESample has been substantially reworked in
#	C++ to show how a "typical" object oriented program could
#	be written. To this end, what was once a single source code
#	file has been restructured into a set of classes which
#	demonstrate the advantages of object-oriented programming.
#
------------------------------------------------------------------------------*/


/*
Segmentation strategy:

    This program has only one segment, since the issues
    surrounding segmentation within a class's methods have
    not been investigated yet. We DO unload the data
    initialization segment at startup time, which frees up
    some memory 

SetPort strategy:

    Toolbox routines do not change the current port. In
    spite of this, in this program we use a strategy of
    calling SetPort whenever we want to draw or make calls
    which depend on the current port. This makes us less
    vulnerable to bugs in other software which might alter
    the current port (such as the bug (feature?) in many
    desk accessories which change the port on OpenDeskAcc).
    Hopefully, this also makes the routines from this
    program more self-contained, since they don't depend on
    the current port setting. 

Clipboard strategy:

    This program does not maintain a private scrap.
    Whenever a cut, copy, or paste occurs, we import/export
    from the public scrap to TextEdit's scrap right away,
    using the TEToScrap and TEFromScrap routines. If we did
    use a private scrap, the import/export would be in the
    activate/deactivate event and suspend/resume event
    routines. 
*/

// Mac Includes
#include <Types.h>
#include <QuickDraw.h>
#include <Fonts.h>
#include <Events.h>
#include <Controls.h>
#include <Windows.h>
#include <Menus.h>
#include <TextEdit.h>
#include <Dialogs.h>
#include <Desk.h>
#include <Scrap.h>
#include <ToolUtils.h>
#include <Memory.h>
#include <SegLoad.h>
#include <Files.h>
#include <OSUtils.h>
#include <Traps.h>

#include "TApplication.h"

// OSEvent is the event number of the suspend/resume and mouse-moved events sent
// by MultiFinder. Once we determine that an event is an osEvent, we look at the
// high byte of the message sent to determine which kind it is. To differentiate
// suspend and resume events we check the resumeMask bit.
const short kOsEvent = app4Evt;				// event used by MultiFinder
const short kSuspendResumeMessage = 0x01;	// high byte of suspend/resume event message
const short kClipConvertMask = 0x02;		// bit of message field clip conversion
const short kResumeMask = 0x01;				// bit of message field for resume vs. suspend
const short kMouseMovedMessage = 0xFA;		// high byte of mouse-moved event message

// Define HiWrd and LoWrd macros for efficiency
#define HiWrd(aLong)	(((aLong) >> 16) & 0xFFFF)
#define LoWrd(aLong)	((aLong) & 0xFFFF)

extern "C" { 
	// from MPW standard library
	void _DataInit(void);				// sets up A5 globals
};

TApplication::TApplication(void)
{
	SysEnvRec envRec;
	long stkNeeded, heapSize;

	// initialize Mac Toolbox components
	InitGraf((Ptr) &qd.thePort);
	InitFonts();
	InitWindows();
	InitMenus();
	TEInit();
	InitDialogs((ResumeProcPtr) nil);
	InitCursor();

	// Unload data segment: note that _DataInit must not be in Main!
	UnloadSeg((Ptr) _DataInit);

	// ignore the error returned from SysEnvirons; even if an error occurred,
	// the SysEnvirons glue will fill in the SysEnvRec
	(void) SysEnvirons(curSysEnvVers, &envRec);

	// Are we running on a 128K ROM machine or better???
	if (envRec.machineType < 0)
	  BigBadError(kErrStrings,eWrongMachine);		// if not, alert & quit

	// if we need more stack space, get it now
	stkNeeded = StackNeeded();
	if (stkNeeded > StackSpace())
	  {
		// new address is heap size + current stack - needed stack
		SetApplLimit((Ptr) ((long) GetApplLimit() - stkNeeded + StackSpace()));
	  }

	// Check for minimum heap size
	heapSize = (long) GetApplLimit() - (long) ApplicZone();
	if (heapSize < HeapNeeded())
	  BigBadError(kErrStrings,eSmallSize);

	// expand the heap so new code segments load at the top
	MaxApplZone();

	// allocate an empty document list
	fDocList = new TDocumentList;

	// check to see if WaitNextEvent is implemented
	fHaveWaitNextEvent = TrapAvailable(_WaitNextEvent, ToolTrap);

	// initialize our class variables
	fCurDoc = nil;
	fDone = false;
	fInBackground = false;
	fMouseRgn = nil;
	fWhichWindow = nil;
}

void TApplication::ExitLoop(void)
{
	fDone = true;
}

void TApplication::EventLoop(void)
{
	int gotEvent;
	EventRecord tEvt;

	SetUp();					// call setup routine
	DoIdle();					// do idle once

	while (fDone == false)
	  {
		// always set up fWhichWindow before doing anything
		fWhichWindow = FrontWindow();
		// see if window belongs to a document
		fCurDoc = fDocList->FindDoc(fWhichWindow);
		// make sure we always draw into correct window
		SetPort(fWhichWindow);

		DoIdle();			// call idle time handler
		
		if (fHaveWaitNextEvent)
		  {
			gotEvent = WaitNextEvent(everyEvent, &tEvt, SleepVal(), fMouseRgn);
		  }
		else
		  {
			SystemTask();
			gotEvent = GetNextEvent(everyEvent, &tEvt);
		  }
		fTheEvent = tEvt;

		// make sure we got a real event
		if ( gotEvent )
		  {
			AdjustCursor();
			switch (fTheEvent.what)
			  {
				case mouseDown :
					DoMouseDown();
					break;
				case mouseUp :
					DoMouseUp();
					break;
				case keyDown :
				case autoKey :
					DoKeyDown();
					break;
				case updateEvt :
					DoUpdateEvt();				
					break;
				case diskEvt :
					DoDiskEvt();
					break;
				case activateEvt :
					DoActivateEvt();
					break;
				case kOsEvent :
					DoOSEvent();
					break;
				default :
					break;
			  } // end switch (fTheEvent.what)
		  }
		AdjustCursor();
	  }
	// call cleanup handler
	CleanUp();
}

void TApplication::DoKeyDown(void)
{
	char key;
	long mResult;

	key = (char) (fTheEvent.message & charCodeMask);
	if ((fTheEvent.modifiers & cmdKey) && (fTheEvent.what == keyDown))
	  {
		// only do command keys if we are not autokeying
		AdjustMenus();					// make sure menus are up to date
		mResult = MenuKey(key);
		if (mResult != 0)				// if it wasn't a menu key, pass it through
		  {
			DoMenuCommand(HiWrd(mResult), LoWrd(mResult));
			return;
		  }
	  }
	if (fCurDoc != nil)
	  {
		EventRecord tEvt;

		// we copy event record so that we don't pass reference to object field 
		tEvt = fTheEvent;
		fCurDoc->DoKeyDown(&tEvt);
	  }
}

void TApplication::DoActivateEvt(void)
{
	// event record contains window ptr
	fWhichWindow = (WindowPtr) fTheEvent.message;
	// see if window belongs to a document
	fCurDoc = fDocList->FindDoc(fWhichWindow);
	SetPort(fWhichWindow);

	if (fCurDoc != nil)
	  fCurDoc->DoActivate((fTheEvent.modifiers & activeFlag) != 0);
}

void TApplication::DoUpdateEvt(void)
{
	// event record contains window ptr
	fWhichWindow = (WindowPtr) fTheEvent.message;
	// see if window belongs to a document
	fCurDoc = fDocList->FindDoc(fWhichWindow);
	SetPort(fWhichWindow);

	if (fCurDoc != nil)
	  fCurDoc->DoUpdate();
}

void TApplication::DoSuspend(Boolean doClipConvert)
{
	doClipConvert = false;		// this is here because I HATE compiler warnings!!
	if (fCurDoc != nil)
	  fCurDoc->DoActivate(!fInBackground);
}

void TApplication::DoResume(Boolean doClipConvert)
{
	doClipConvert = false;		// this is here because I HATE compiler warnings!!
	if (fCurDoc != nil)
	  fCurDoc->DoActivate(!fInBackground);
}

void TApplication::DoOSEvent(void)
{
	Boolean doConvert;
	unsigned char evType;

	// is it a multifinder event?
	evType = (fTheEvent.message >> 24) & 0x00ff;
	switch (evType) { 	// high byte of message is type of event
		case kMouseMovedMessage :
			DoIdle();					// mouse-moved is also an idle event
			break;
		case kSuspendResumeMessage :
			doConvert = (fTheEvent.message & kClipConvertMask) != 0;
			fInBackground = (fTheEvent.message & kResumeMask) == 0;
			if (fInBackground)
			  DoSuspend(doConvert);
			else DoResume(doConvert);
			break;
	}
}

void TApplication::DoMouseDown(void)
{
	long mResult;
	short partCode;
	WindowPtr tWind;
	EventRecord tEvt;

	// gotta watch those object field dereferences
	partCode = FindWindow(fTheEvent.where, &tWind);
	fWhichWindow = tWind;
	tEvt = fTheEvent;
	switch (partCode)
	  {
		case inSysWindow :
			DoMouseInSysWindow();
			break;
		case inMenuBar :
			AdjustMenus();
			mResult = MenuSelect(tEvt.where);
			if (mResult != 0)
			  DoMenuCommand(HiWrd(mResult),LoWrd(mResult));
			break;
		case inGoAway :
			DoGoAway();					
			break;
		case inDrag :
			DoDrag();
			break;
		case inGrow :
			if (fCurDoc != nil)
			  fCurDoc->DoGrow(&tEvt);					
			break;
		case inZoomIn :
		case inZoomOut :
			if ((TrackBox(fWhichWindow, tEvt.where, partCode)) &&
				(fCurDoc != nil))
			  fCurDoc->DoZoom(partCode);
			break;
		case inContent :
			// If window is not in front, make it so
			if ( fWhichWindow != FrontWindow() )
			  SelectWindow(fWhichWindow);
			else if (fCurDoc != nil)
			  fCurDoc->DoContent(&tEvt);					
			break;
	  }
}

void TApplication::DoDrag(void)
{
	DragWindow(fWhichWindow, fTheEvent.where, &qd.screenBits.bounds);
}

void TApplication::DoGoAway(void)
{
	if (TrackGoAway(fWhichWindow, fTheEvent.where))
	  {
		if (fCurDoc != nil)
		  {
			fDocList->RemoveDoc(fCurDoc);
			delete fCurDoc;
		  }
		else CloseDeskAcc(((WindowPeek) fWhichWindow)->windowKind);
	  }
}

Boolean TApplication::TrapAvailable(short tNumber,TrapType tType)
{
	// Check and see if the trap exists. On 64K ROM machines, tType will be ignored.
	return NGetTrapAddress(tNumber, tType) != GetTrapAddress(_Unimplemented);
} /*TrapAvailable*/

void AlertUser(short errResID, short errCode)
{
	Str255 message;

	SetCursor(&qd.arrow);
	GetIndString(message, errResID, errCode);
	ParamText(message, "\p", "\p", "\p");
	(void) Alert(rUserAlert, (ModalFilterProcPtr) nil);
} // AlertUser

void BigBadError(short errResID, short errCode)
{
	AlertUser(errResID,errCode);
	ExitToShell();
}

// That's all, folks...
